from cranesched.solution import Solution


class SlidingWindow(object):
    """Given a large intractable instance of the crane scheduling problem, create and solve a 
    sequence of smaller instances (taken as a subset of the larger instance). These smaller 
    instances are solved sequentially, and at each step a number of assignments is fixed and used 
    to provide the initial state for the cranes in the next sub-instance/slice.
    
    Argument 'slice_size' controls the number of activities to take from the activity sequence, 
    and 'slice_step' defines how many actions are fixed from one slice/instance to the next.
    
    The activities in each subinstance are taken, sequentially, from the list 'assignable', which 
    contains only activities without unassigned predecessor activities. 
    The end date of an activity's predecessors must also be respected when the activity is assigned.
    For instance, one or more predecessors were assigned in a previous subinstance, and the current 
    subinstance must take into account the completion dates of all predecessors in order to avoid 
    inconsistent states (e.g. a crane trying to pick up an item from a stack before the item was 
    placed there)."""
    verbose = True
    
    def __init__(self, instance, solver, slice_size=10, slice_step=1):
        self.solver = solver
        self.instance = instance
        self.subinstance = None
        self.solution = Solution(instance)
        self.action2activity = dict((a.action, a) for a in instance.activities)
        
        self.sequence = list(sequence)
        self.activity2index = dict((a, i) for i, a in enumerate(self.sequence))
        self.slice_size = slice_size
        self.slice_step = slice_step
        
    def next_subinstance(self):
        index_of = self.activity2index
        assigned = self.solution.activities
        activities = set()
        indices = [index_of[a] for a in self.instance.activities - assigned]
        indices.sort(reverse=True)
        if len(indices) == 0:
            return None
        while len(indices) > 0 and len(activities) < self.slice_size:
            index = indices.pop()
            activity = self.sequence[index]
            if len(activity.pred - assigned - activities) == 0:
                activities.add(activity)
                indices.extend(index_of[s] for s in activity.succ)
                indices.sort(reverse=True)
        subinstance = self.instance.extract(activities)
        subinstance.cranes = set(c.deepcopy() for c in self.solution.cranes.itervalues())
        return subinstance
        
    def step(self, iterations=1, verbose=None):
        if verbose is not None:
            self.verbose = bool(verbose)
        N = len(self.instance.activities)
        for _ in xrange(iterations):
            self.subinstance = self.next_subinstance()
            if self.subinstance is None:
                return "solution completed"
                
            M = len(self.solution.assignments)
            if self.verbose:
                print ("%d assignments done, %d left. Solving subinstance %s" % 
                       (M, N - M, sorted(a.id for a in self.subinstance.activities)))
            self.solver.driver.reset()
            self.solver.driver.init(self.subinstance)
            best = self.solver.driver.run()
            
            for assignment in best.data.assignments[:self.slice_step]:
                activity = self.action2activity[assignment.action]
                crane = self.solution.cranes[assignment.crane.id]
                if self.verbose:
                    print ("Fixing assignment Action#%s -> Crane#%s" % 
                           (activity.id, crane.id))
                self.solution.assign(activity, crane)
            self.solution.router.clear_stack()
            
    def run(self, verbose=None):
        if verbose is not None:
            self.verbose = bool(verbose)
        self.step(len(self.instance.activities))
        
        


def subinstance(size, prec_graph, assigned=()):
    activities = set()
    index = {a: i for i, a in enumerate(prec_graph)}
    manager = ActivityManager(prec_graph, assigned)
    for _ in xrange(size):
        activity = min(manager.assignable, key=index.__getitem__)
        activities.append(activity)
        manager.mark_assigned(activity)
    return activities
    
